package campus.aiit.ssl.service;

import campus.aiit.ssl.dao.*;
import campus.aiit.ssl.domain.model.*;
import campus.aiit.ssl.manage.custom.dao.DaoProxy;
import campus.aiit.ssl.manage.modbus.ModBus;
import campus.aiit.ssl.manage.modbus.ModBusCommand;
import campus.aiit.ssl.manage.modbus.SocketForModBus;
import campus.aiit.ssl.manage.modbus.provider.InstructionPriorityException;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import com.iflytek.uaac.client.rest.context.UaacServiceContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.util.*;
import java.util.function.Consumer;

/**
 * @author ihaokun
 * @date 2019/12/12 15:02
 */
@Service
public class DeviceService {
    private final static boolean passed = false;

    @Resource
    private ModBusCommand modBusCommand;

    private DeviceDao deviceDao;
    private InstructTemplateDao templateDao;
    private InstructDao instructDao;
    private CollectLogDao collectLogDao;
    private SchoolZoneDao schoolZoneDao;
    private BuildingDao buildingDao;
    private RoomDao roomDao;
    private DeviceModelDao deviceModelDao;
    private DeviceTypeDao deviceTypeDao;
    @Resource
    private InstructLogDao instructLogDao;
    @Resource
    private LogService logService;

    @Autowired
    public DeviceService(DeviceDao deviceDao,
                         InstructTemplateDao templateDao,
                         InstructDao instructDao,
                         CollectLogDao collectLogDao,
                         SchoolZoneDao schoolZoneDao,
                         BuildingDao buildingDao,
                         RoomDao roomDao,
                         DeviceModelDao deviceModelDao,
                         DeviceTypeDao deviceTypeDao) {
        this.deviceDao = deviceDao;
        this.templateDao = templateDao;
        this.instructDao = instructDao;
        this.collectLogDao = collectLogDao;
        this.schoolZoneDao = schoolZoneDao;
        this.buildingDao = buildingDao;
        this.roomDao = roomDao;
        this.deviceModelDao = deviceModelDao;
        this.deviceTypeDao = deviceTypeDao;
    }

    @PostConstruct
    public void afterConstruct() {
        deviceDao = new DaoProxy<DeviceDao>().getProxy(deviceDao);
        instructDao = new DaoProxy<InstructDao>().getProxy(instructDao);
    }

    public boolean addDevice(Device device) {

        //  device.setId((long)new Random().nextInt(999999999));
        deviceDao.insertSelective(device);

        //    Device deviceinfo = deviceDao.selectbycodeipport(device.getIpAddress(), device.getPort().toString(), device.getAddressCode());

//      CollectLog collectLog = new CollectLog();
//
//      collectLog.setDeviceId(deviceinfo.getId());
//
//      collectLogDao.insertSelective(collectLog);

        try {
               //实时在线状态
            // 异步请求
            new Thread(()->{
                logService.writeDeviceLog();
            }).start();
//            collectLogDao.selectByForeignKey(deviceinfo.getId());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    public boolean modifyDevice(Device device) {
        deviceDao.updateByPrimaryKeySelective(device);
        return true;
    }

    public boolean removeDevice(long id) {
        deviceDao.deleteByPrimaryKey(id);
        return true;
    }

    @Transactional
    public List<Map<String, Object>> devices(Long zoneId, Long buildingId, Long floor, Long roomId, Long typeId, String userId, boolean isInstant) {
        List<Map<String, Object>> result = deviceDao.selectByLocation(zoneId, buildingId, floor, roomId, typeId, userId);
        if (isInstant) {
            logService.writeDeviceLog();
        }

        result.forEach(map -> {
            long id = (long) map.get("id");
            CollectLog collectLog = collectLogDao.selectByForeignKey(id);
            Instruct Instruct[] = instructDao.selectByDeviceId(id);
            map.put("log", collectLog);
            map.put("Instruct", Instruct);
        });
        return result;
    }

    public boolean autoTurnOn(long[] ids) {
        return autoBatchOperate(ids, 1, sdu -> modBusCommand.turnOnAuto(sdu));
    }

    public boolean autoTurnOff(long[] ids) {
        return autoBatchOperate(ids, 0, sdu -> modBusCommand.turnOffAuto(sdu));
    }

    @Transactional
    private boolean autoBatchOperate(long[] ids, int status, Consumer<SocketForModBus.SDU> consumer) {
        for (long id : ids) {
            Device device = deviceDao.selectByPrimaryKey(id);
            SocketForModBus.SDU sdu = fillSduByDevice(device);
            consumer.accept(sdu);
        }
        for (int i = 0; i < ids.length; i++) {
            CollectLog collectLog = new CollectLog();
            collectLog.setDeviceId(ids[i]);
            collectLog.setAutoStatus((byte) status);
            collectLogDao.updateDeviceStatus(collectLog);
        }
        return true;
    }

    public boolean relayTurnOn(long[] ids) {
        return relayBatchOperate(ids, 1, sdu -> modBusCommand.turnOnRelay(sdu));
    }

    public boolean relayTurnOff(long[] ids) {
        return relayBatchOperate(ids, 0, sdu -> modBusCommand.turnOffRelay(sdu));
    }

    @Transactional
    private boolean relayBatchOperate(long[] ids, int status, Consumer<SocketForModBus.SDU> consumer) {
        for (long id : ids) {
            // 注意：开启有蜂鸣器响声；关闭继电器会有延时，无蜂鸣器响声，但有咔哒的声音
            Device device = deviceDao.selectByPrimaryKey(id);
            // 优先级原因：自控 > 继电器 > others
            SocketForModBus.SDU sdu = fillSduByDevice(device);
            if (modBusCommand.autoStatus(sdu)) {
                throw new InstructionPriorityException("device auto-status is on, cannot on or off relay");
            }
            // on or off by given method argument consumer
            consumer.accept(sdu);
        }
        for (int i = 0; i < ids.length; i++) {
            CollectLog collectLog = new CollectLog();
            collectLog.setDeviceId(ids[i]);
            collectLog.setRelayStatus((byte) status);
            if (status == 0) {
                collectLog.setOnOffStatus((byte) status);
            }
            collectLogDao.updateDeviceStatus(collectLog);
        }
        return true;
    }

    private SocketForModBus.SDU fillSduByDevice(Device device) {
        SocketForModBus.SDU sdu = new SocketForModBus.SDU(device.getIpAddress(), device.getPort());
        ModBus.ADU adu = new ModBus.ADU();
        adu.setAddress(device.getAddressCode());
        sdu.setAdu(adu);
        return sdu;
    }

    private SocketForModBus.SDU fillSdu(String host, int port, String address) {
        SocketForModBus.SDU sdu = new SocketForModBus.SDU(host, port);
        ModBus.ADU adu = new ModBus.ADU();
        adu.setAddress(address);
        sdu.setAdu(adu);
        return sdu;
    }

    public List<String> items() {
        return instructDao.firstInstructNames();
    }

    @Transactional
    public boolean performItem(long[] deviceIds, String itemName, HttpSession httpSession) {
//    Map<String, Object> resultMap = new HashMap<>();
        for (long deviceId : deviceIds) {
            Device device = deviceDao.selectByPrimaryKey(deviceId);
            Long templateId = templateDao.selectAll()[0].getId();       //默认值
            templateId = templateDao.selectIdByForeignKeys(device.getTypeId(), device.getModelId());
            Instruct instruct = instructDao.selectItem(templateId, itemName);
            boolean result = modBusCommand.infrared(device.getIpAddress(), device.getPort(), device.getAddressCode(), instruct.getValue());
//      if (!result){
//        System.err.println(deviceId + "设备执行命令失败");
//        resultMap.put("deviceId", deviceId + "设备执行命令失败");
//      }
            // write instruct log
            InstructLog instructLog = new InstructLog();
            instructLog.setUserId(UaacServiceContext.getUaacService().getCurrentLoginName(httpSession));
            instructLog.setDeviceId(deviceId);
            instructLog.setInstructId(instruct.getId());
            instructLog.setResult((byte) (result ? 0 : 1));
            instructLogDao.insertSelective(instructLog);

            CollectLog collectLog = new CollectLog();
            collectLog.setDeviceId(deviceId);
            collectLog.setOnOffStatus((byte) (itemName.equals("关机") ? 0 : 1));
            collectLogDao.updateDeviceStatus(collectLog);
        }
        return true;
    }

    public boolean controlByInstructId(Long deviceId, Long instructId, HttpSession httpSession) {
        Device device = deviceDao.selectByPrimaryKey(deviceId);
        Instruct instruct = instructDao.selectInstructById(instructId);

        boolean result = modBusCommand.infrared(device.getIpAddress(), device.getPort(), device.getAddressCode(), instruct.getValue());
        InstructLog instructLog = new InstructLog();
        instructLog.setUserId(UaacServiceContext.getUaacService().getCurrentLoginName(httpSession));
        instructLog.setDeviceId(deviceId);
        instructLog.setInstructId(instruct.getId());
        instructLog.setResult((byte) (result ? 0 : 1));
        instructLogDao.insertSelective(instructLog);

        CollectLog collectLog = new CollectLog();
        collectLog.setDeviceId(deviceId);
        collectLog.setOnOffStatus((byte) (instruct.getName().equals("关机") ? 0 : 1));
//    collectLogDao.updateDeviceStatus(collectLog);
        return result;
    }

    //弃用，批量操作，理由同下
    @Deprecated
    @Transactional
    public boolean reverseAutoStatus(long[] ids) {
        for (long id : ids) {
            Device device = deviceDao.selectByPrimaryKey(id);
            SocketForModBus.SDU sdu = fillSduByDevice(device);
            if (modBusCommand.autoStatus(sdu)) {
                if (!modBusCommand.turnOffAuto(sdu)) return false;
            } else {
                if (!modBusCommand.turnOnAuto(sdu)) return false;
            }
        }
        return true;
    }

    //弃用，批量操作，不翻转，全部开或全部关
    @Deprecated
    @Transactional
    public boolean reverseRelayStatus(long[] ids) {
        for (long id : ids) {
            // 注意：开启有蜂鸣器响声；关闭继电器会有延时，无蜂鸣器响声，但有咔哒的声音
            Device device = deviceDao.selectByPrimaryKey(id);
            // 优先级原因：自控 > 继电器 > others
            SocketForModBus.SDU sdu = fillSduByDevice(device);
            if (modBusCommand.autoStatus(sdu)) {
                throw new InstructionPriorityException("device auto-status is on, cannot off relay");
            } else {
                if (modBusCommand.relayStatus(sdu)) {
                    if (!modBusCommand.turnOffRelay(sdu)) return false;
                } else {
                    if (!modBusCommand.turnOnRelay(sdu)) return false;
                }
            }
        }
        return true;
    }

    public Instruct[] Instructs(Long typeId) {
        return instructDao.selectByTypeId(typeId);
    }

    @Transactional
    public List<Map<String, Object>> selectByBlock(Long blockId, Long roomId, Long typeId, String userId, boolean isInstant) {
        List<Map<String, Object>> result = deviceDao.selectByBlock(blockId, roomId, typeId, userId);

        if (isInstant) {
            logService.writeDeviceLog();
        }

        result.forEach(map -> {
            long id = (long) map.get("id");
            CollectLog collectLog = collectLogDao.selectByForeignKey(id);
//            CollectLog collectLog1 = new CollectLog();
//            collectLog1.setZigbeeStatus(5);
//            collectLog1.setZigbeeStatusFs(5);
            Instruct[] Instruct = instructDao.selectByDeviceId(id);
            map.put("log", collectLog);
            map.put("Instruct", Instruct);
        });
        return result;
    }

    public Map<String, Object> checkFile(MultipartFile file) {
        Map<String, Object> map = new HashMap<>();
        List<Map<String, Object>> ckMessages = new ArrayList<>();
        List<Map<String, Object>> uniqueKeys = getUniqueKeys();
        List<Device> devices = new ArrayList<>();

        if (file == null) {
            map.put("result", "文件为空");
            ckMessages.add(map);
            map.put("ckMessages", ckMessages);
            return map;
        }
        ImportParams params = new ImportParams();
        params.setTitleRows(0);
        params.setHeadRows(1);
        List<DeviceExcel> list;
        try {
            int i = 1;
            list = ExcelImportUtil.importExcel(file.getInputStream(), DeviceExcel.class, params);
            for (DeviceExcel d : list
            ) {
                Map<String, Object> ckMessage = new HashMap<>();
                ckMessage.put("result", passed);

                Device device = new Device();
                SchoolZone[] schoolZone = schoolZoneDao.selectArray();
                int s = 1;
                for (SchoolZone zone : schoolZone) { //核实校区名是否存在
                    if (d.getSchoolZone().equals(zone.getName())) {
                        s = 1;
                        System.out.println("第" + i + "行校区检验通过！");
                        Building[] buildings = buildingDao.selectArrayByForeignKey(zone.getId());
                        int b = 1;
                        for (Building building : buildings) { //核实楼栋名是否存在
                            if (d.getBuilding().equals(building.getName())) {
                                b = 1;
                                System.out.println("第" + i + "行楼栋检验通过！");
                                if (Integer.parseInt(d.getFloor()) < building.getFloors()) { //核实楼层数是否正确
                                    System.out.println("第" + i + "行楼层检验通过！");
                                    Room[] rooms = roomDao.selectArrayByForeignKeyFloor(building.getId(), Long.parseLong(d.getFloor()));
                                    int r = 1;
                                    for (Room room : rooms) {//核实区域是否存在
                                        if (d.getRoom().equals(room.getName())) {
                                            r = 1;
                                            System.out.println("第" + i + "行教室检验通过！" + room.getName());
                                            device.setRoomId(room.getId());
                                            DeviceModel[] models = deviceModelDao.selectALl();
                                            int m = 1;
                                            for (DeviceModel model : models
                                            ) {
                                                if (d.getBrand().equals(model.getBrandName()) && d.getModelId().equals(model.getModelName())) {
                                                    m = 1;
                                                    System.out.println("第" + i + "行品牌型号检验通过！" + model.getBrandName() + model.getModelName());
                                                    device.setModelId(model.getId());
                                                    DeviceType[] types = deviceTypeDao.selectAll();
                                                    int t = 1;
                                                    for (DeviceType type : types
                                                    ) {
                                                        if (d.getType().equals(type.getName())) {
                                                            t = 1;
                                                            System.out.println("device:" + d.getType() + "--type:" + type.getName());
                                                            System.out.println("第" + i + "行设备类型检验通过！" + type.getName());
                                                            device.setTypeId(type.getId());
                                                            int k = 1;
                                                            for (Map<String, Object> key : uniqueKeys
                                                            ) {
                                                                if (k >= uniqueKeys.size()) {
                                                                    device.setIpAddress(d.getIpAddress());
                                                                    device.setPort(d.getPort());
                                                                    device.setAddressCode(d.getAddressCode());
                                                                    device.setName(d.getName());
                                                                    System.out.println("第" + i + "行检验通过！" + device.getName());
                                                                    ckMessage.put("result", !passed);
                                                                    ckMessage.put("message", "第" + i + "行检验通过！");
                                                                    devices.add(device);
                                                                } else if (d.getIpAddress().equals(key.get("ipAddress")) && d.getPort().equals(key.get("port")) && d.getAddressCode().equals(key.get("addressCode"))) {
                                                                    System.out.println("第" + i + "行存在IP地址+端口+设备地址不唯一！");
                                                                    System.out.println("ip:" + key.get("ipAddress") + "--port:" + key.get("port"));
                                                                    ckMessage.put("result", passed);
                                                                    ckMessage.put("message", "第" + i + "行存在IP地址-端口-设备地址不唯一！");
                                                                    break;
                                                                }
                                                                k++;
                                                            }
                                                            break;
                                                        } else if (t >= types.length) {
                                                            System.out.println("t:" + t + "--types:" + types.length + "--else-if:device:" + d.getType() + "--type:" + type.getName());
                                                            ckMessage.put("result", passed);
                                                            ckMessage.put("message", "第" + i + "行存在设备类型数据不合法！");
                                                        }
                                                        t++;
                                                    }
                                                } else if (m >= models.length && (!d.getBrand().equals(model.getBrandName()) || !d.getModelId().equals(model.getModelName()))) {
                                                    ckMessage.put("result", passed);
                                                    ckMessage.put("message", "第" + i + "行存在品牌型号数据不合法！");
                                                }
                                                m++;
                                            }
                                            break;
                                        } else if (r >= rooms.length) {
                                            System.out.println(d.getRoom() + room.getName() + r);
                                            ckMessage.put("result", passed);
                                            ckMessage.put("message", "第" + i + "行存在教室数据不合法！");
                                        }
                                        r++;
                                    }
                                } else {
                                    ckMessage.put("result", passed);
                                    ckMessage.put("message", "第" + i + "行存在楼层数据不合法!！");
                                }
                            } else if (!d.getBuilding().equals(building.getName()) && b >= buildings.length) {
                                ckMessage.put("result", passed);
                                ckMessage.put("message", "第" + i + "行存在楼栋数据不合法!！");
                            }
                            b++;
                        }
                    } else if (!d.getSchoolZone().equals(zone.getName()) && s >= schoolZone.length) {
                        ckMessage.put("result", passed);
                        ckMessage.put("message", "第" + i + "行存在校区数据不合法!！");
                    }
                    s++;
                }
                i++;
                ckMessages.add(ckMessage);
            }
            map.put("ckMessages", ckMessages);
            map.put("devices", devices);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

    private List<Map<String, Object>> getUniqueKeys() {
        return deviceDao.selectUniqueKeys();
    }
}